	option casemap :none                        ;# Case sensitive
	.486                                        ;# Create 32 bit code
	.model flat, stdcall                        ;# 32 bit memory model
	.code

;调用该代码前的栈状态
; esp 内容
;   0 FunctionEntryData结构体的地址
;  +4 原函数的返回地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……

;调用该代码时的栈状态
; ebp 内容
;  -? pushad保存的所有32位寄存器
;  -8 函数返回值
;  -4 FunctionEntryData结构体的地址
;   0 旧的ebp
;  +4 原函数的返回地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……

;调用普通函数（非retval）前的栈
; esp 内容
;  -8 欲调用的函数
;  -4 this指针 或 FunctionEntryData结构体的地址
;   0 原函数的返回地址
;  +4 this指针 或 FunctionEntryData结构体的地址
;  +8 原函数的参数1
;  +C 原函数的参数2
;     ……


    FEF_CheckIDE = 1
        ;是否检查VB IDE（通过调用pfnCheckIDE）。
        ;若处于VB IDE的调试模式，则试图调用pfnOldProc。
        ;若pfnOldProc不存在，则自己修正栈并返回。
    
    FEF_StructPtr = 2
        ;是否传递FunctionEntryData结构的地址。
            ;当存在该标志时，表示参数列中第一个参数是FunctionEntryData结构的地址，而不传送this指针。
            ;该标志用于某些复杂设计，这样可以在标准模块中编写转发函数以处理复杂情况。
    
    FEF_RetVal = 80000000h
        ;是否有返回值。
        ;COM的返回值是在栈中的，需要特别处理（所以需要该标志）。注意只支持返回简单类型（既返回值能放入EAX寄存器）。
        ;对于传统的通过EAX寄存器返回结果的函数，则不需要该标志。


FunctionEntryData	struc
	ASMCodeRet	dq	?	;存放返回指令，因为需要从不同参数个数的函数中返回
	fFlags		dd	?	;标志常数
	pfnCheckIDE	dd	?	;检查VB IDE的函数（在调用CreateFunctionEntry()时自动设置该成员）
	pfnProc		dd	?	;欲调用的函数（会调整参数列，如插入this指针）
	pfnOldProc	dd	?	;原来的函数（不会调整参数列，与原来的函数原型一致）
	pObjThis	dd	?	;this指针（VarPtr(对象变量)的返回值）
	cArgs		dd	?	;有多少个参数（函数的参数是多少个DWORD）
FunctionEntryData	ends

;ASMCodeRet字段的代码：
;61        popad
;8B 45 F8  mov eax, dword ptr [ebp-8]
;C9        leave
;C2 34 12  ret 1234h


;参数
_retEIP$ = 4	;返回地址
_pThis$ = 4	;this指针 或 FunctionEntryData结构体的地址
_Arg0$ = 8	;参数0
;局部变量
_pData$ = -4	;FunctionEntryData结构体的地址 或 this指针（仅非retval函数最后调用pfnProc函数时）
_lResult$ = -8	;返回值。仅retval函数
_pfnCall$ = -8  ;调用的函数的地址（由于popad，只能通过栈传递数据）。仅非retval函数及处理FEF_CheckIDE时
start:
	;为调试作准备
	nop

	;恢复栈
	pop	eax	;将Data结构的地址放入eax寄存器中

	;保存环境
	push	ebp
	mov	ebp, esp

	;分配局部变量
	sub	esp, 8

	;所有寄存器进栈
	pushad

	;初始化局部变量
	mov	ebx, eax	;以后通过ebx来访问Data结构
	mov	DWORD PTR _pData$[ebp], eax	;将Data结构的地址写入内存
	xor	eax, eax	;初始化返回值
	mov	DWORD PTR _lResult$[ebp], eax

	;检查FEF_CheckIDE常数（无标志则跳转）
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_CheckIDE
	jz	_no_stopped

	;检查pfnCheckIDE函数
	mov	eax, [ebx + FunctionEntryData.pfnCheckIDE]
	test	eax, eax
	jz	_no_stopped

	;调用pfnCheckIDE函数
	call	eax
	cmp	eax, 2
	jne	_no_stopped

;处于VB IDE运行环境的中断模式
_is_stopped:
	;检查pfnOldProc函数
	mov	eax, [ebx + FunctionEntryData.pfnOldProc]
	test	eax, eax
	jz	_no_oldproc
	mov	DWORD PTR _pfnCall$[ebp], eax	;将函数地址放入栈中

	;调用
	popad
	mov	eax, DWORD PTR _pfnCall$[ebp]
	mov	esp, ebp
	pop	ebp
	jmp	eax

;没有默认处理函数
_no_oldproc:
	;调用返回代码
	lea	eax, [ebx + FunctionEntryData.ASMCodeRet]
	jmp	eax



;不是处于VB IDE运行环境的中断模式
_no_stopped:
	;检查FEF_RetVal常数（带标志则跳转）
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_RetVal
	jnz	_is_retval_fun

;普通函数（无返回值 或 返回值能放在在eax寄存器中）
_is_normal_fun:
	;将调用函数的地址保存在栈中
	mov	edx, [ebx + FunctionEntryData.pfnProc]
	mov	DWORD PTR _pfnCall$[ebp], edx

	;检查FEF_StructPtr常数（带标志则跳转）
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_StructPtr
	jnz	_call_normal_fun

	;将this指针保存在栈中
	mov	eax, [ebx + FunctionEntryData.pObjThis]
	mov	DWORD PTR _pData$[ebp], eax

;调用普通函数
_call_normal_fun:
	;恢复栈
	popad
	mov	esp, ebp
	pop	ebp

	;填充this指针
	push	[esp]	;重复栈顶元素（返回地址）
	mov	eax, DWORD PTR _pData$[esp]
	mov	DWORD PTR _pThis$[esp], eax

	;调用
	mov	eax, DWORD PTR _pfnCall$[esp]
	jmp	eax



;带COM返回值的函数
_is_retval_fun:
	;将返回值的地址压进栈
	lea	eax, DWORD PTR _lResult$[ebp]
	push	eax

	;调整ecx,使其等于要复制的DWORD数

	;计算要复制的字节数（eax）、双字数（ecx）
	mov	edx, [ebx + FunctionEntryData.cArgs]
	mov	eax, edx
	mov	cl, 2	;现在是32位代码，每个参数占4字节——既左移2位
	shl	eax, cl
	mov	ecx, edx

	;空出栈（等下用串指令将参数复制过来）
	sub	esp, eax	;空出栈

	;复制参数
	cld					;清方向位，使地址正向增加
	lea	esi, DWORD PTR _Arg0$[ebp]	;计算源指针
	mov	edi, esp			;设置目的指针
	rep	movsd				;复制

	;检查FEF_StructPtr常数（带标志则跳转）
	mov	edx, ebx	;默认为FEF_StructPtr模式
	mov	eax, [ebx + FunctionEntryData.fFlags]
	test	eax, FEF_StructPtr
	jnz	_call_retval_fun

	;将edx改成this指针
	mov	edx, [ebx + FunctionEntryData.pObjThis]

_call_retval_fun:
	;调用
	push	edx
	call	DWORD PTR [ebx + FunctionEntryData.pfnProc]

	;返回
	lea	eax, [ebx + FunctionEntryData.ASMCodeRet]
	jmp	eax

end start